<?php

namespace Org\Util;

/**
 * 参数检验类
 * @modify trainyao 2015-09-29 创建
 */
class Validate {

    /**
     * 错误返回信息
     * @var array
     * @access public
     */
    public $errorReturn = array();

    /**
     * 私有变量,用来保存使用动态规则的现场
     * @var array
     * @access public
     */
    public $ruleDump = NULL;

    /**
     * 私有变量,保存公共校验规则
     * @var array
     * @access public
     */
    public $publicRule = NULL;

    /**
     * 私有变量,默认规则
     * @modify trainyao 2015-10-14 添加notnull,moreEqualToday,moreToday,moreEqualNow,moreNow规则
     * @modify trainyao 2015-10-15 修改password username的正则
     * @var array
     * @access public
     */
    public $rule = array(
        // 非空校验
        'notempty' => array('callback', 'notEmptyString', 'is empty'),
        'notnull' => array('callback', 'notNull', 'is null'),
        // 用户信息
        'password' => array('regex', "/^(?=.*?[a-zA-Z])(?=.*?[0-9])[a-zA-Z0-9]{6,20}$/", 'wrong password format'),
        'username' => array('regex', '/^([a-zA-Z0-9\-_\x{4e00}-\x{9fa5}][\sa-zA-Z0-9\-_\x{4e00}-\x{9fa5}]{0,20})$/u',
            'wrong username format'),
        'IDCard' => array('callback', "validateIdCard", 'wrong IDCard format'),
        // 时间格式检测
        'time' => array('regex', '/^[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}$/',
            'wrong time format'),
        'date' => array('regex', '/^[\d]{4}-[\d]{2}-[\d]{2}$/', 'wrong date format'),
        'timestamp' => array('regex', '/^[\d]{10}$/', 'wrong timtstamp format'),
        'lessEqualToday' => array('callback', 'lessEqualToday', false), // 特殊处理
        'lessToday' => array('callback', 'lessToday', false), // 特殊处理
        'moreEqualToday' => array('callback', 'moreEqualToday', false), // 特殊处理
        'moreToday' => array('callback', 'moreToday', false), // 特殊处理
        'lessEqualNow' => array('callback', 'lessEqualNow', false), // 特殊处理
        'lessNow' => array('callback', 'lessNow', false), // 特殊处理
        'moreEqualNow' => array('callback', 'moreEqualNow', false), // 特殊处理
        'moreNow' => array('callback', 'moreNow', false), // 特殊处理
        // 固定格式字符串格式检测
        'email' => array('regex', '/^([0-9A-Za-z\\-_\\.]+)@([0-9A-Za-z]+\\.[a-zA-Z0-9]+(\\.[a-zA-Z]{2})?)$/',
            'wrong email format'),
        'telephone' => array('regex', "/^([0-9]{3,4}-)?[0-9]{7,8}$/", 'wrong telephone format'),
        'mobile' => array('regex', "/^1[34578]{1}\d{9}$/", 'wrong mobile format'),
        'concatString' => array('regex', "/^(.+[,])*(.+)$/", 'wrong concatString format'),
        'gender' => array('regex', "/^[0-1]{1}$/", "wrong gender data"),
        // 数据类型检测
        'boolean' => array('function', 'is_bool', 'wrong boolean format'),
        'booleanInt' => array('regex', "/^[01]{1}$/", 'wrong booleanInt format'),
        'decimal' => array('regex', "/^(\d+(\.\d{1,2})?)$/i", 'wrong decimal format'),
        'positiveInteger' => array('regex', "/^([1-9]{1}\d*)$/i", 'wrong positiveInteger format'), // 正负整数
        'integer' => array('regex', "/^(\-)?([1-9]{1}\d*)|(0)$/i", 'wrong integer format'), // 正负整数
        'number' => array('regex', "/^(((\-)?[1-9]{1}\d*(\.\d+)?)|(0))$/i", 'wrong number format'), // 正负整/小数
        'natureNumber' => array('regex', "/^([1-9]{1}\d*)|(0)$/", 'wrong natureNumber format'), // 自然数
        'positiveNumber' => array('regex', "/^[1-9]{1}\d*(\.\d+)?$/", 'wrong positiveNumber format'), // 正整/小数
        // 字符检验
        'chinese' => array('regex', "/([\x81-\xfe][\x40-\xfe])/", 'wrong chinese format'),
    );

    /**
     * 构造函数,创建检验类对象,并加载自定义检验规则,自定义规则将覆盖默认规则
     * 自定义规则写在config.php文件内,并以VALIDATE_UTIL_CUSTOM_RULE为key
     * @modify trainyao 2015-09-29 创建
     */
    public function __construct() {
        // 初始化自定义校验规则
        $customRule = C('VALIDATE_UTIL_CUSTOM_RULE');
        if (!empty($customRule) && is_array($customRule)) {
            foreach ($customRule as $ruleTag => $ruleKey) {
                // 校验方法和校验规则为空,且该验证规则存在 的时候,判断为更新错误返回码
                if (is_null($ruleKey[0]) && is_null($ruleKey[1]) && !empty($this->rule[$ruleTag])) {
                    $this->rule[$ruleTag][2] = $ruleKey[2];
                } else {
                    $this->rule[$ruleTag] = $ruleKey;
                }
            }
        }
        // 初始话公共校验规则
        $publicRule = C('VALIDATE_UTIL_PUBLIC_RULE');
        if (!is_null($publicRule)) {
            if (is_array($publicRule)) {
                $publicRule = implode(',', $publicRule);
            }
            $this->publicRule = $publicRule;
        }
    }

    /**
     * 执行校验
     * @modify trainyao 2015-09-28 创建
     * @modify trainyao 2015-10-09 更正返回类型注释,补充注释
     * @param mixed $value 待校验值
     * @param array $rule 传入校验规则
     * @param array $ruleAdd 该次校验传入的规则
     * @return boolean 校验成功与否
     */
    public static function validate($value, $rule, $ruleAdd = NULL) {
        // 初始化校验类全局变量
        global $__globalValidateUtil;
        if (!$__globalValidateUtil instanceof Validate) {
            $__globalValidateUtil = new Validate();
        }

        if (!empty($ruleAdd)) {
            // 保存现场
            $__globalValidateUtil->ruleDump = $__globalValidateUtil->rule;
            $__globalValidateUtil->addRule($ruleAdd);
        }

        $validateResult = false;
        if (is_array($value)) {
            $validateResult = $__globalValidateUtil->validateAll($value, $rule);
        } else {
            $validateResult = $__globalValidateUtil->validateOne($value, $rule);
        }

        if (!empty($ruleAdd)) {
            // 还原现场
            $__globalValidateUtil->rule = $__globalValidateUtil->ruleDump;
        }

        if (!$validateResult) {
            $errorMsg = $__globalValidateUtil->getError();
            throw new \Exception($errorMsg['msg'], 1);
            return false;
        }
        $__globalValidateUtil->errorReturn = array();
        return true;
    }

    /**
     * 校验单个参数
     * @modify trainyao 2015-09-29 创建
     * @modify trainyao 2015-10-09 添加mblength校验规则
     * @modify trainyao 2015-10-14 处理function,callback传入concat形式参数
     * @param mixed $value 传入校验值
     * @param array $rule 传入校验规则
     * @return mixed 校验成功返回该值,校验失败返回信息数组
     */
    public function validateOne($value, $rule) {
        // 如果存在公共校验规则,加上一并校验
        if (!empty($this->publicRule)) {
            $rule = $this->publicRule . ',' . $rule;
        }

        $rule = $this->getExplodeArray($rule);
        foreach ($rule as $ruleTag) {
            // 用以验证的规则数组 : array($validateType, $validateKey, $errorMsg)
            $ruleItem = $this->rule[$ruleTag];
            if (empty($ruleItem)) {
                $this->errorReturn['validate'] = $ruleTag;
                $this->errorReturn['msg'] = 'rule not exists.';
                return false;
            }
            // 保存失败时返回的错误信息数组
            $this->errorReturn['validate'] = $ruleTag;
            $this->errorReturn['msg'] = $ruleItem[2];
            $validatePass = false;

            switch (strtolower($ruleItem[0])) {
                case 'in':
                    $inValueArray = $this->getExplodeArray($ruleItem[1]);
                    $validatePass = in_array($value, $inValueArray) ? true : false;
                    break;
                case 'between':
                    $betweenArray = $this->getExplodeArray($ruleItem[1]);

                    if (count($betweenArray) < 2) {
                        return array('validate' => $ruleTag, 'msg' => 'wrong rule format');
                    }
                    $validatePass = ($value >= $betweenArray[0] && $value <= $betweenArray[1]) ? true : false;
                    break;
                case 'function':
                    // 处理逗号分割情况
                    $functionArray = $this->getExplodeArray($ruleItem[1]);
                    if (count($functionArray) > 1) {
                        $functionName = $functionArray[0];
                        // $functionArray变成参数数组
                        $functionArray[0] = $value;
                        $validatePass = call_user_func_array($functionName, $functionArray) ? true : false;
                    } else {
                        $valueArray = array($value);
                        $functionName = $functionArray[0];
                        $validatePass = call_user_func_array($functionName, $valueArray) ? true : false;
                    }
                    break;
                case 'callback':
                    $functionName = NULL;
                    $valueArray = array();

                    // 处理逗号情况
                    $functionArray = $this->getExplodeArray($ruleItem[1]);
                    if (count($functionArray) > 1) {
                        $functionName = $functionArray[0];
                        $functionArray[0] = $value;
                        $valueArray = $functionArray;
                    } else {
                        $functionName = $functionArray[0];
                        $valueArray = array($value);
                    }

                    $validatePass = call_user_func_array(array(&$this, $functionName), $valueArray) ? true : false;
                    break;
                case 'length':
                    $lengthArray = $this->getExplodeArray($ruleItem[1]);
                    if (count($lengthArray) >= 2) {
                        $valueLength = strlen($value);
                        $validatePass = ( $valueLength >= $lengthArray[0] && $valueLength <= $lengthArray[1] ) ? true : false;
                    } else {
                        $validatePass = ( strlen($value) == $ruleItem[1] ) ? true : false;
                    }
                    break;
                case 'mblength':
                    $lengthArray = $this->getExplodeArray($ruleItem[1]);
                    // 区分校验规则是 length,encoding 还是 minlen,maxlen,encoding 格式
                    if (count($lengthArray) >= 3) {
                        $encoding = $lengthArray[2];
                        $valueLength = mb_strlen($value, $encoding);
                        $validatePass = ($valueLength >= $lengthArray[0] && $valueLength <= $lengthArray[1]) ? true : false;
                    } else {
                        $length = $lengthArray[0];
                        $encoding = $lengthArray[1];
                        $validatePass = (mb_strlen($value, $encoding) == $length ) ? true : false;
                    }
                    break;
                case 'ruleor':
                    $ruleArray = $this->getExplodeArray($ruleItem[1]);
                    $ruleOnePass = $this->validateOne($value, $ruleArray[0]);
                    $ruleTwoPass = $this->validateOne($value, $ruleArray[1]);

                    $validatePass = ($ruleOnePass || $ruleTwoPass) ? true : false;
                    break;
                // 默认使用正则
                default :
                    $validatePass = preg_match($ruleItem[1], $value) ? true : false;
            }

            // 校验不通过
            if (!$validatePass) {
                return false;
            }
        }
        return true;
    }

    /**
     * 校验参数数组
     * @modify trainyao 2015-09-29 创建
     * @param array $valueArray 传入校验数组
     * @param array $ruleArray 传入校验规则数组
     * @return mixed 校验成功返回该值,校验失败返回信息数组
     */
    public function validateAll($valueArray, $ruleArray) {
        if (!is_array($ruleArray)) {
            $this->errorReturn['validate'] = 'none';
            $this->errorReturn['msg'] = 'wrong multi rule format';
            return false;
        }

        $validatePass = false;
        foreach ($ruleArray as $paramKey => $validateRule) {
            // 如果值不存在,需要检验notnull,则报错   (支持存在该值才校验的情况)
            if (!isset($valueArray[$paramKey])) {
                if (strpos($validateRule, 'notnull') !== false) {
                    $this->errorReturn['validate'] = 'notnull';
                    $this->errorReturn['msg'] = $this->rule['notnull'][2];
                    return false;
                } else {
                    continue;
                }
            }
            $value = $valueArray[$paramKey];
            $this->errorReturn['key'] = $paramKey;
            $validatePass = $this->validateOne($value, $validateRule);
            if (!$validatePass) {
                return false;
            }
        }
        return true;
    }

    /**
     * 返回出错信息
     * @modify tranyao 2015-09-29 创建
     * @return array 出错信息数组
     */
    public function getError($type = '', $param = '') {
        switch (strtolower($type)) {
            case 'explode':
                $this->errorReturn['msg'] = explode($param, $this->errorReturn['msg']);
                break;
            case 'json':
                $this->errorReturn['msg'] = json_encode($this->errorReturn['msg']);
                break;
            default:
        }
        return $this->errorReturn;
    }

    /**
     * 拓展校验规则数组,根据传入的规则
     * @modify tranyao 2015-09-28 创建
     * @param array $ruleAdd 拓展数组
     * @return 无
     */
    public function addRule($ruleAdd) {
        foreach ($ruleAdd as $ruleTag => $ruleKeyArray) {
            $this->rule[$ruleTag] = $ruleKeyArray;
        }
    }

    /**
     * 私有工具方法:检查是否为数组,若不是,用分割字符串进行explode,返回
     * @modify trainyao 2015-09-29 创建
     * @modify trainyao 2015-10-14 可传入,修改分割字符串
     * @param mixed $input 数组或字符串
     * @return array 输出分割后的数组
     */
    private function getExplodeArray($input, $delimiter = ',') {
        if (is_array($input)) {
            return $input;
        } else {
            return explode($delimiter, $input);
        }
    }

    /**
     * 私有校验函数,校验身份证
     * @modify trainyao 2015-09-29 从function移植过来
     * @param mixed $idCard 身份证号
     * @return boolean 校验结果
     */
    protected function validateIdCard($idCard) {
        /*if (!preg_match('/^[a-zA-z0-9]{0,18}$/', $idCard)) {
            return false;
        } else {
            return true;
        }*/

        $vCity = array(
            '11', '12', '13', '14', '15', '21', '22',
            '23', '31', '32', '33', '34', '35', '36',
            '37', '41', '42', '43', '44', '45', '46',
            '50', '51', '52', '53', '54', '61', '62',
            '63', '64', '65', '71', '81', '82', '91',
        );
        if (!preg_match('/^([\d]{17}[xX\d]|[\d]{15})$/', $idCard)) {
            return false;
        }
        if (!in_array(substr($idCard, 0, 2), $vCity)) {
            return false;
        }
        $idCard = preg_replace('/[xX]$/i', 'a', $idCard);
        $vLength = strlen($idCard);
        if ($vLength == 18) {
            $vBirthday = substr($idCard, 6, 4) . '-' . substr($idCard, 10, 2) . '-' . substr($idCard, 12, 2);
        } else {
            $vBirthday = '19' . substr($idCard, 6, 2) . '-' . substr($idCard, 8, 2) . '-' . substr($idCard, 10, 2);
        }
        if (date('Y-m-d', strtotime($vBirthday)) != $vBirthday) {
            return false;
        }
        if ($vLength == 18) {
            $vSum = 0;
            for ($i = 17; $i >= 0; $i--) {
                $vSubStr = substr($idCard, 17 - $i, 1);
                $vSum += (pow(2, $i) % 11) * (($vSubStr == 'a') ? 10 : intval($vSubStr, 11));
            }

            if ($vSum % 11 != 1) {
                return false;
            }
        }
        return true;
    }

    /**
     * 私有工具函数,校验时间是否小于等于今天
     * @modify trainyao 2015-09-29 创建
     * @param  string $time 待校验时间字符串
     * @return boolean 检验结果
     */
    protected function lessEqualToday($time) {
        $tomorrowStart = strtotime(date('Ymd')) + 86400;
        $valueTimestamp = strtotime($time);
        if (!$valueTimestamp) {
            return false;
        }
        return ( $valueTimestamp < $tomorrowStart) ? true : false;
    }

    /**
     * 私有工具函数,校验时间是否小于今天
     * @modify trainyao 2015-09-29 创建
     * @param  string $time 待校验时间字符串
     * @return boolean 检验结果
     */
    protected function lessToday($time) {
        $todayStart = strtotime(date('Ymd'));
        $valueTimestamp = strtotime($time);
        if (!$valueTimestamp) {
            return false;
        }
        return ( $valueTimestamp < $todayStart) ? true : false;
    }

    /**
     * 私有工具函数,校验时间是否小于等于现在的时间
     * @modify trainyao 2015-09-29 创建
     * @param  string $time 待校验时间字符串
     * @return boolean 检验结果
     */
    protected function lessEqualNow($time) {
        $now = time();
        $valueTimestamp = strtotime($time);
        if (!$valueTimestamp) {
            return false;
        }
        return ( $valueTimestamp <= $now) ? true : false;
    }

    /**
     * 私有工具函数,校验时间是否小于现在的时间
     * @modify trainyao 2015-09-29 创建
     * @param  string $time 待校验时间字符串
     * @return boolean 检验结果
     */
    protected function lessNow($time) {
        $now = time();
        $valueTimestamp = strtotime($time);
        if (!$valueTimestamp) {
            return false;
        }
        return ( $valueTimestamp < $now) ? true : false;
    }

    /**
     * 私有工具函数,校验日期是否大于等于今天
     * @modify trainyao 2015-10-14 创建
     * @param  mixin $time 待校验时间
     * @return boolean 校验结果
     */
    protected function moreEqualToday($time) {
        $isLessToday = $this->lessToday($time);
        return $isLessToday ? false : true;
    }

    /**
     * 私有工具函数,校验日期是否大于等于今天
     * @modify trainyao 2015-10-14 创建
     * @param  mixin $time 待校验时间
     * @return boolean 校验结果
     */
    protected function moreToday($time) {
        $isLessEqualToday = $this->lessEqualToday($time);
        return $isLessEqualToday ? false : true;
    }

    /**
     * 私有工具函数,校验时间是否大于等于当前时间
     * @modify trainyao 2015-10-14 创建
     * @param  mixin $time 待校验时间
     * @return boolean 校验结果
     */
    protected function moreEqualNow($time) {
        $isLessNow = $this->lessNow($time);
        return $isLessNow ? false : true;
    }

    /**
     * 私有工具函数,校验时间是否大于当前时间
     * @modify trainyao 2015-10-14 创建
     * @param  mixin $time 待校验时间
     * @return boolean 校验结果
     */
    protected function moreNow($time) {
        $isLessEqualNow = $this->lessEqualNow($time);
        return $isLessEqualNow ? false : true;
    }

    /**
     * 私有工具函数,校验NULL值,返回值与php is_null函数相反
     * @modify trainyao 2015-10-13 创建
     * @param mixin $value 待校验值
     * @return boolean 是否不是NULL值
     */
    protected function notNull($value) {
        if (is_null($value)) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 私有工具函数,校验空字符串
     * @modify trainyao 2015-10-19 创建
     * @param mixin $value 待校验值
     * @return boolean 是否不是空字符串
     */
    protected function notEmptyString($value) {
        if($value === '') {
            return false;
        }
        return true;
    }
}
